/* filename : FFDecklinkPool.h
 * 다음을 개선하였다.
 * 11. memory를 get할 때 retain check하여 release처리를 먼저하고 memory를 주소를 return 하도록 수정
 * 12. mHead는 NULL이 될 수 있고, mTail을 NULL이 되지 않도록 처리하여 Pool이 비어있는지 확인하도록 수정하였음
 */
#ifndef __FFDECKLINKPOOL_H__
#define __FFDECKLINKPOOL_H__

#include <string.h>
#include <pthread.h>

#define DEFAULT_NUMBER 100

/* FFDeckLinkQueueMemory
 * FFDeckLinkPool에 할당될 메모리 하나의 structure
 * 같은 데이터를 여러번 참조 할 수 있기 때문에 retainCount가 있고
 * 메모리는 동적으로 할당한다.
 * Class가 종료되기 전에 해제한다.
 */
typedef struct FFDeckLinkQueueMemory{
	/* mem
	 * 동적 할 당되어있는 memroy pointer
	 */
	uint8_t *mem;

	/* retainCount
	 * 참조하고 있는 개수
	 * 같은 데이터를 여러번 재사용할 경우 사용
	 * Audio 나 Video를 여러 profile로 출력할 때 같은 데이터를 여러번 읽는다.
	 */
	uint8_t retainCount;

	/* next
	 * 다음 FFDeckLinkQueueMemory 를 가리키는 pointer
	 */
	FFDeckLinkQueueMemory *next;
}FFDeckLinkQueueMemory;

/* FFDeckLinkPool
 * DeckLink에서 만들어진 Audio Video Stream을 복사하여 기억할 수 있는 미리 할당한 memroy pool이다.
 */
class FFDeckLinkPool
{
public:
	/* FFDeckLinkPool(); 생성자
	 * 모든 맴버 변수 0으로 초기화. 단, mInitRetainCount만 1로 초기화
	 * mMutex도 초기화
	 */
	FFDeckLinkPool(void);

	/* ~FFDeckLinkPool(); 소멸자
	 * this pool에 할당한 FFDeckLinkQueueMemory를 모두 해제
	 * mMutex 파괴
	 */
	~FFDeckLinkPool(void);

	/* size_t init(size_t memSize);
	 * Parameters:
	 *    memSize; 메모리 하나당 크기(audio 1024*2*2, video 1920*1080*2)
	 * Returns:
	 *    size_t; 메모리가 할당된 총 크기
	 *
	 *    DEFAULT_NUMBER로 init함수 호출
	 */
	size_t init(size_t memSize);

	/* size_t init(int nb, size_t memSize);
	 * Parameters:
	 *         nb; 할당할 메모리 개수
	 *    memSize; 메모리 하나당 크기(audio 1024*2*2, video 1920*1080*2)
	 * Returns:
	 *    size_t; 메모리가 할당된 총 크기
	 */
	size_t init(int nb, size_t memSize);

	/* void flush();
	 * Parameters:
	 *    void;
	 * Returns:
	 *    void;
	 *
	 * this pool에 할당한 FFDeckLinkQueueMemory를 모두 해제
	 */
	void flush(void);

	/* void setInitRetainCount(uint8_t retainCount);
	 * Parameters:
	 *    retainCount; FFDeckLinkQueueMemory를 몇번 참조 할 것인지 설정
	 * Returns:
	 *    void;
	 * 설정한 숫자 만큼 release를 해야 메모리가 반환된(사용하지 않는 상태)다.
	 */
	void setInitRetainCount(uint8_t retainCount);

	/* void status()
	 * Parameters:
	 *    void;
	 * Returns:
	 *    void;
	 * 현재 pool의 상태를 알려준다.
	 * 할당된 개수, 사용중인개수, 첫 메모리 주소 등...
	 */
	void status(void);

	/* uint8_t* get();
	 * Parameters:
	 *    void;
	 * Returns:
	 *    uint8_t*;
	 *
	 * thread safe를 위해 get하기전에 pop함수를 호출한다.
	 * 현재 사용중인지 확인하고(retaincount) 사용중이지 않으며
	 * 할당된 FFDeckLinkQueueMemory를 하나 알려준다.
	 * 더이상 할당되어있는 FFDeckLinkQueueMemory가 없으면 강제 프로그램이 종료된다.
	 */
	uint8_t* get(void);

	/* uint8_t release(uint8_t* mem);
	 * Parameters:
	 *    mem; 반환할 memory의 주소
	 * Returns:
	 *    uint8_t; 반환한 memory의 retain count
	 *
	 * mem의 주소를 갖고 있는 FFDeckLinkQueueMemory를 찾아서 retain count를 하나 줄인다.
	 * 여기서 실제로 메모리를 사용 가능한 상태를 만들지 않고 get()할 때 상태를 변경한다.
	 */
	uint8_t release(uint8_t* mem);

	/* uint8_t retain(uint8_t* mem);
	 *    mem: retain Count를 증가할 memory 주소
	 * Returns:
	 *    uint8_t; 증가한 memory의 retain count
	 *
	 * mem의 주소를 갖고 있는 FFDeckLinkQueueMemory를 찾아서 retain count를 하나 늘린다.
	 */
	uint8_t retain(uint8_t* mem);

	/* float rateSize(void);
	 * Parameters:
	 *    void;
	 * Returns:
	 *    float; memory를 사용중인 비율
	 *
	 * memory를 사용중인 배율을 개산한다.
	 * 사용중인개수 / 총 할당된 개수
	 */
	float rateSize(void);

	//temp
	int isvideo;

private:

	/* mFirst
	 * FFDeckLinkQueueMemory의 첫 주소
	 * 초기값: NULL
	 * init 후에 NULL이 되면 안된다.
	 */
	FFDeckLinkQueueMemory *mFirst;

	/* mHead
	 * FIFO의 처음 주소
	 * 초기값: NULL
	 * 사용할 수 있는 mem이 없으면 NULL이다.
	 * 이 변수로 this pool이 비었는지 알 수 있다.
	 */
	FFDeckLinkQueueMemory *mHead;

	/* mTail
	 * FIFO의 마지막 주소
	 * 초기값: NULL
	 * init 후에 NULL이 되면 안된다.
	 */
	FFDeckLinkQueueMemory *mTail;

	/* mNbTotal
	 * FFDeckLinkQueueMemory가 할당된 총 개수
	 */
	int mNbTotal;

public:
	/* mUseNB
	 * FFDeckLinkQueueMemory를 사용중인 총 개수
	 */
	int mUseNB;

private:
	/* mTatalSize
	 * FFDeckLinkQueueMemory가 할당된 크기
	 */
	size_t mTotalSize;

	/* mUseSize
	 * FFDeckLinkQueueMemory가 사용중인 크기
	 */
	size_t mUseSize;

	/* mSizePerMem
	 * FFDeckLinkQueueMemory의 하나당 크기
	 */
	size_t mSizePerMem;

	/* mInitRetain Count
	 * 한번 get()할때 참조할 개수
	 * release를 mInitRetainCount만큼 해야 FFDeckLinkQueueMemory가 사용가능한 상태로 변할 수 있다.
	 */
	uint8_t mInitRetainCount;

	/* mMutex
	 * pthread 동기화 context
	 * lock, unlock할 때 사용
	 */
	pthread_mutex_t mMutex;
	//pthread_cond_t mCond;
	//pthread_cond_t mCond =   PTHREAD_COND_INITIALIZER; //pthread_cont_t 초기화 방법 1
	//pthread_cond_init(mCond);                          //pthread_cont_t 초기화 방법 2

	/* void pop();
	 * Parameters:
	 *    void;
	 * Returns:
	 *    void;
	 *
	 * this pool에서 FFDeckLinkQueueMemory의 retain coun가 0인 것을 조사하여
	 * FFDeckLinkQueueMemory를 사용 가능한 상태로 만들어주는 함수
	 */
	void pop(void);
};

FFDeckLinkPool::FFDeckLinkPool()
{
	mFirst = mHead = mTail = NULL;

	mNbTotal = mUseNB = 0;
	mTotalSize = mUseSize = mSizePerMem = 0;

	mInitRetainCount = 1;

	pthread_mutex_init(&mMutex, NULL);
}

FFDeckLinkPool::~FFDeckLinkPool()
{
	pthread_mutex_destroy(&mMutex);

	if(mFirst == NULL)
	{
		logI("OK Already flush queue.\n");
	}
	else
	{
		logW("Not yet flush memory. Call FFDeckLinkPool:flush()\n");
		flush();
	}
}

size_t FFDeckLinkPool::init(size_t memSize)
{
	return init(DEFAULT_NUMBER, memSize);
}

size_t FFDeckLinkPool::init(int nb, size_t memSize)
{
	FFDeckLinkQueueMemory *q = NULL;
	FFDeckLinkQueueMemory *last_q = NULL;

	if(mFirst != NULL)
	{
		logE("already alloc memory %d", mNbTotal);
		return 0;
	}

	for(mNbTotal = 0; mNbTotal < nb; mNbTotal++)
	{
		if(NULL == (q = (FFDeckLinkQueueMemory*)av_malloc(sizeof(FFDeckLinkQueueMemory))))
		{
			logE("FFDeckLinkQueueMemory fail av malloc\n");
			return 0;
		}

		if(NULL == (q->mem = (uint8_t*)av_malloc(memSize)))
		{
			logE("mem of FFDeckLinkQueueMemory fail av malloc\n");
			return 0;
		}
		mTotalSize += memSize + sizeof(FFDeckLinkQueueMemory);

		q->retainCount = 0;

		if(mFirst == NULL)
		{
			mFirst = q;
		}
		else
		{
			last_q->next = q;
		}

		last_q = q;
	}
	mTail = last_q;
	mSizePerMem = memSize;
	last_q->next = mFirst;

	return mTotalSize;
}

void FFDeckLinkPool::flush(void)
{
	FFDeckLinkQueueMemory *q = NULL;
	FFDeckLinkQueueMemory *next = NULL;

	for (q = mFirst; 0 < mNbTotal; mNbTotal--, q = next)
	{
		next = q->next;
		av_free(q->mem);
		av_free(q);
		q = NULL;
	}
	mFirst = NULL;
	mHead = NULL;
	mTail = NULL;
	mUseNB = 0;
	mUseSize = 0;
	mNbTotal = 0;
	mTotalSize = 0;
}

void FFDeckLinkPool::setInitRetainCount(uint8_t retainCount)
{
	mInitRetainCount = retainCount;
}

void FFDeckLinkPool::status(void)
{
	logD("NB %d/%d size %ld/%ld f %p h %p t %p\n", mUseNB, mNbTotal, mUseSize, mTotalSize, mFirst, mHead, mTail);
}

// queue push
uint8_t* FFDeckLinkPool::get(void)
{
	if(mHead == NULL)
	//사용할 수 있는 mem이 없으면
	{
		mHead = mTail->next;
	}
	else
	{
		//For thread safe: 다 사용되어진 메모리를 사용할수있도록 mHead 조절
		switch (pthread_mutex_trylock(&mMutex))
		{
			case 0:
				//On success. this thread locked.
				pop();
				pthread_mutex_unlock(&mMutex);
			break;
			case EBUSY:
				logD("The mutex is already locked.\n");

				break;
			case EINVAL:
				logD("mutex is not an initialized mutex.\n");
				break;
			case EFAULT:
				logD("mutex is an invalid pointer.\n");
				break;
			default:
				break;
		}
	}

	if(mTail->next->next == mHead)
		//pool을 가득채워지지 않도록 방지(가득채운것에 한개 비운다)
	{
		if(isvideo) status();
		if(isvideo) logF("FULL pool\n");
		exit(1);
	}
	else
	{
		mTail = mTail->next;
	}

	mTail->retainCount = mInitRetainCount;
	mUseNB++;
	mUseSize += mSizePerMem + sizeof(FFDeckLinkQueueMemory);

	return mTail->mem;
}

// queue pop
void FFDeckLinkPool::pop(void)
{
	FFDeckLinkQueueMemory *q = NULL;

	for(q = mHead; q != mTail->next; q = q->next)
	{
		if(q->retainCount <= 0)
		{
			mUseNB--;
			mUseSize -= mSizePerMem + sizeof(FFDeckLinkQueueMemory);
		}
		else
		{
			break;
		}
	}
	mHead = q;
}

uint8_t FFDeckLinkPool::release(uint8_t* mem)
{
	FFDeckLinkQueueMemory *q = NULL;

	if(mHead == NULL)
	{
		logE("mHead NULL\n");
		exit(1);
	}
	for(q = mHead; q != mTail->next; q = q->next)
	{
		//For thread safe, mHead, mTail은 수정하지 않는다.
		if(q->mem == mem)
		{
			pthread_mutex_lock(&mMutex);
			q->retainCount--;
			pthread_mutex_unlock(&mMutex);

			return q->retainCount;
		}
	}

	logF("Not found mem %p\n", mem);
	exit(1);
}

uint8_t FFDeckLinkPool::retain(uint8_t* mem)
{
	FFDeckLinkQueueMemory *q = NULL;
	int i = 0;
	q = mHead;
	while(true)
	{
		if(q->mem == mem)
		{
			return ++q->retainCount;
		}
		q = q->next;
		i++;
		if(i > mUseNB)
		{
			logE("Not searching mem %p\n", mem);
			return 0;
		}
	}
}

float FFDeckLinkPool::rateSize(void)
{
	return mNbTotal?mUseNB/mNbTotal:0;
}

#endif //ifndef
